#include "dspSimulator/instruction/ADD.h"

namespace dspsimulator {
    void ADD::generate_ir(const std::shared_ptr<simulator::IRGenElement> &ir_gen_element) {
        std::shared_ptr<simulator::LLVMJit> llvm_jit = ir_gen_element->m_llvm_jit;
        RegIRCopy &reg_ir_copy = ir_gen_element->m_reg_ir_copy;
        std::shared_ptr<llvm::IRBuilder<>> ir_builder = llvm_jit->ir_builder();
        // ADD .func imm32,src2_reg,dst_reg
        auto op1 = std::dynamic_pointer_cast<Immediate32>(m_op1);
        auto op2 = std::dynamic_pointer_cast<SingleReg>(m_op2);
        auto op3 = std::dynamic_pointer_cast<SingleReg>(m_op3);

        if (op1 && op2 && op3) {
            u32 src1 = op1->data();
            std::string src2_name = get_reg_name(op2->reg());
            llvm::Value *src2 = reg_ir_copy[src2_name];
            std::string dst_name = get_reg_name(op3->reg());
            llvm::Value *dst_ptr = IRGenerator::get_global_value_ptr(llvm_jit, dst_name);
            llvm::Value *result = ir_builder->CreateAdd(ir_builder->getInt32(src1), src2);
            ir_builder->CreateStore(result, dst_ptr);
        }
    }

    std::shared_ptr<DspInstruction>
    ADD::decode_standard_instruction(const std::shared_ptr<DspInstructionInfo> &dsp_instruction_info) {
        std::shared_ptr<ADD> add;
        std::bitset<32> &data = dsp_instruction_info->m_standard_bits;
        if (common::check_bits<10>(data, 0b0000010110, 2) &&
            exist_standard_condition(common::extract_bits<4>(data, 28))) {
            // ADD .L scst5,xsint,sint
            add = std::make_shared<ADD>(dsp_instruction_info->address(), 4);
            u32 src1 = common::extract_bits<5>(data, 13).to_ulong();
            u32 src2 = common::extract_bits<5>(data, 18).to_ulong();
            u32 dst = common::extract_bits<5>(data, 23).to_ulong();
            bool s = data[1];
            bool x = data[12];
            add->m_p = data[0];
            add->m_creg_z = common::extract_bits<4>(data, 28);
            add->m_func_unit = s ? FuncUnit::L1 : FuncUnit::L2;
            add->m_op1 = std::make_shared<Immediate32>(common::extract_bit_to_int<5>(src1));
            add->m_op2 = std::make_shared<SingleReg>(get_src2_reg32_kind(src2, s, x));
            add->m_op3 = std::make_shared<SingleReg>(get_dst_reg32_kind(dst, s));
        } else if (common::check_bits<11>(data, 0b01001010000, 2) &&
                   exist_standard_condition(common::extract_bits<4>(data, 28))) {
            // ADD .D sint,ucst5,sint
            add = std::make_shared<ADD>(dsp_instruction_info->address(), 4);
            u32 src1 = common::extract_bits<5>(data, 13).to_ulong();
            u32 src2 = common::extract_bits<5>(data, 18).to_ulong();
            u32 dst = common::extract_bits<5>(data, 23).to_ulong();
            bool s = data[1];
            add->m_p = data[0];
            add->m_creg_z = common::extract_bits<4>(data, 28);
            add->m_func_unit = s ? FuncUnit::D1 : FuncUnit::D2;
            add->m_op1 = std::make_shared<Immediate32>(common::extract_bit_to_int<5>(src1));
            add->m_op2 = std::make_shared<SingleReg>(get_src2_reg32_kind(src2, s));
            add->m_op3 = std::make_shared<SingleReg>(get_dst_reg32_kind(dst, s));
        } else if (common::check_bits<11>(data, 0b01000010000, 2) &&
                   exist_standard_condition(common::extract_bits<4>(data, 28))) {
            // ADD .D sint,sint,sint
            add = std::make_shared<ADD>(dsp_instruction_info->address(), 4);
            u32 src1 = common::extract_bits<5>(data, 13).to_ulong();
            u32 src2 = common::extract_bits<5>(data, 18).to_ulong();
            u32 dst = common::extract_bits<5>(data, 23).to_ulong();
            bool s = data[1];
            add->m_p = data[0];
            add->m_creg_z = common::extract_bits<4>(data, 28);
            add->m_func_unit = s ? FuncUnit::D1 : FuncUnit::D2;
            add->m_op1 = std::make_shared<SingleReg>(get_src1_reg32_kind(src1, s));
            add->m_op2 = std::make_shared<SingleReg>(get_src2_reg32_kind(src2, s));
            add->m_op3 = std::make_shared<SingleReg>(get_dst_reg32_kind(dst, s));
        } else if (common::check_bits<10>(data, 0b0000011110, 2) &&
                   exist_standard_condition(common::extract_bits<4>(data, 28))) {
            // ADD .L sint.xsint,sint
            add = std::make_shared<ADD>(dsp_instruction_info->address(), 4);
            u32 src1 = common::extract_bits<5>(data, 13).to_ulong();
            u32 src2 = common::extract_bits<5>(data, 18).to_ulong();
            u32 dst = common::extract_bits<5>(data, 23).to_ulong();
            bool s = data[1];
            bool x = data[12];
            add->m_p = data[0];
            add->m_creg_z = common::extract_bits<4>(data, 28);
            add->m_func_unit = s ? FuncUnit::L1 : FuncUnit::L2;
            add->m_op1 = std::make_shared<SingleReg>(get_src1_reg32_kind(src1, s));
            add->m_op2 = std::make_shared<SingleReg>(get_src2_reg32_kind(src2, s, x));
            add->m_op3 = std::make_shared<SingleReg>(get_dst_reg32_kind(dst, s));
        } else if (exist_standard_condition(common::extract_bits<4>(data, 28)) &&
                   common::check_bits<10>(data, 0b0001101000, 2)) {
            add = std::make_shared<ADD>(dsp_instruction_info->address(), 4);
            // ADD .S scst5,xsint,sint
            u32 src1 = common::extract_bits<5>(data, 13).to_ulong();
            u32 src2 = common::extract_bits<5>(data, 18).to_ulong();
            u32 dst = common::extract_bits<5>(data, 23).to_ulong();
            bool s = data[1];
            bool x = data[12];
            add->m_p = data[0];
            add->m_creg_z = common::extract_bits<4>(data, 28);
            add->m_func_unit = s ? FuncUnit::S1 : FuncUnit::S2;
            add->m_op1 = std::make_shared<Immediate32>(common::extract_bit_to_int<5>(src1));
            add->m_op2 = std::make_shared<SingleReg>(get_src2_reg32_kind(src2, s, x));
            add->m_op3 = std::make_shared<SingleReg>(get_dst_reg32_kind(dst, s));
        } else if (exist_standard_condition(common::extract_bits<4>(data, 28)) &&
                   common::check_bits<10>(data, 0b0001111000, 2)) {
            add = std::make_shared<ADD>(dsp_instruction_info->address(), 4);
            // ADD .S sint,xsint,sint
            u32 src1 = common::extract_bits<5>(data, 13).to_ulong();
            u32 src2 = common::extract_bits<5>(data, 18).to_ulong();
            u32 dst = common::extract_bits<5>(data, 23).to_ulong();
            bool s = data[1];
            bool x = data[12];
            add->m_p = data[0];
            add->m_creg_z = common::extract_bits<4>(data, 28);
            add->m_func_unit = s ? FuncUnit::S1 : FuncUnit::S2;
            add->m_op1 = std::make_shared<SingleReg>(get_src1_reg32_kind(src1, s));
            add->m_op2 = std::make_shared<SingleReg>(get_src2_reg32_kind(src2, s, x));
            add->m_op3 = std::make_shared<SingleReg>(get_dst_reg32_kind(dst, s));
        } else if (exist_standard_condition(common::extract_bits<4>(data, 28)) &&
                   common::check_bits<10>(data, 0b1010101100, 2)) {
            add = std::make_shared<ADD>(dsp_instruction_info->address(), 4);
            // ADD .D sint,xsint,sint
            u32 src1 = common::extract_bits<5>(data, 13).to_ulong();
            u32 src2 = common::extract_bits<5>(data, 18).to_ulong();
            u32 dst = common::extract_bits<5>(data, 23).to_ulong();
            bool s = data[1];
            bool x = data[12];
            add->m_p = data[0];
            add->m_creg_z = common::extract_bits<4>(data, 28);
            add->m_func_unit = s ? FuncUnit::D1 : FuncUnit::D2;
            add->m_op1 = std::make_shared<SingleReg>(get_src1_reg32_kind(src1, s));
            add->m_op2 = std::make_shared<SingleReg>(get_src2_reg32_kind(src2, s, x));
            add->m_op3 = std::make_shared<SingleReg>(get_dst_reg32_kind(dst, s));
        } else if (exist_standard_condition(common::extract_bits<4>(data, 28)) &&
                   common::check_bits<10>(data, 0b1010111100, 2)) {
            add = std::make_shared<ADD>(dsp_instruction_info->address(), 4);
            // ADD .D scst5,xsint,sint
            u32 src1 = common::extract_bits<5>(data, 13).to_ulong();
            u32 src2 = common::extract_bits<5>(data, 18).to_ulong();
            u32 dst = common::extract_bits<5>(data, 23).to_ulong();
            bool s = data[1];
            bool x = data[12];
            add->m_p = data[0];
            add->m_creg_z = common::extract_bits<4>(data, 28);
            add->m_func_unit = s ? FuncUnit::D1 : FuncUnit::D2;
            add->m_op1 = std::make_shared<Immediate32>(common::extract_bit_to_int<5>(src1));
            add->m_op2 = std::make_shared<SingleReg>(get_src2_reg32_kind(src2, s, x));
            add->m_op3 = std::make_shared<SingleReg>(get_dst_reg32_kind(dst, s));
        }
        return add;
    }

    std::shared_ptr<DspInstruction>
    ADD::decode_compact_instruction(const std::shared_ptr<DspInstructionInfo> &dsp_instruction_info) {
        std::shared_ptr<ADD> add;
        std::bitset<16> &data = dsp_instruction_info->m_compact_bits;
        if (common::check_bits<6>(data, 0b011110, 10) && common::check_bits<6>(data, 0b110111, 1)) {
            add = std::make_shared<ADD>(dsp_instruction_info->address(), 2);
            u32 src1 = -1;
            u32 src2 = common::extract_bits<3>(data, 7).to_ulong();
            u32 dst = src2;
            bool s = data[0];
            add->m_func_unit = s ? FuncUnit::S1 : FuncUnit::S2;
            add->m_op1 = std::make_shared<Immediate32>(src1);
            add->m_op2 = std::make_shared<SingleReg>(get_src2_reg32_kind(src2, s));
            add->m_op3 = std::make_shared<SingleReg>(get_dst_reg32_kind(dst, s));
            add->m_p = dsp_instruction_info->p();
        } else if (common::check_bits<3>(data, 0b101, 13) && common::check_bits<3>(data, 0b110, 10) &&
                   common::check_bits<2>(data, 0b11, 5) && common::check_bits<2>(data, 0b11, 1)) {
            // LSDx1
            add = std::make_shared<ADD>(dsp_instruction_info->address(), 2);
            u32 src = common::extract_bits<3>(data, 7).to_ulong();
            u32 dst = src;
            std::bitset<2> unit = common::extract_bits<2>(data, 3);
            bool s = data[0];
            if (unit == 0b00) {
                add->m_func_unit = s ? FuncUnit::L1 : FuncUnit::L2;
            } else if (unit == 0b01) {
                add->m_func_unit = s ? FuncUnit::S1 : FuncUnit::S2;
            } else if (unit == 0b10) {
                add->m_func_unit = s ? FuncUnit::D1 : FuncUnit::D2;
            } else {
                return nullptr;
            }
            add->m_op1 = std::make_shared<SingleReg>(get_src1_reg32_kind(src, s));
            add->m_op2 = std::make_shared<Immediate32>(1);
            add->m_op3 = std::make_shared<SingleReg>(get_dst_reg32_kind(dst, s));
            add->m_p = dsp_instruction_info->p();
        } else if (data[10] && common::check_bits<3>(data, 0b000, 1)) {
            add = std::make_shared<ADD>(dsp_instruction_info->address(), 2);
            u32 cst = static_cast<u32>(compute_cst5_by_cst3(common::extract_bits<3>(data, 13), data[11]));
            bool x = data[12];
            u32 src2 = common::extract_bits<3>(data, 7).to_ulong();
            u32 dst = common::extract_bits<3>(data, 4).to_ulong();
            bool s = data[0];
            add->m_func_unit = s ? FuncUnit::L1 : FuncUnit::L2;
            add->m_op1 = std::make_shared<Immediate32>(cst);
            add->m_op2 = std::make_shared<SingleReg>(get_src2_reg32_kind(src2, s, x));
            add->m_op3 = std::make_shared<SingleReg>(get_dst_reg32_kind(dst, s));
            add->m_p = dsp_instruction_info->p();
        }
        return add;
    }

    std::string ADD::to_string() const {
        return get_standard_condition(m_creg_z) + m_name + " " + get_function_unit_name(m_func_unit) + " " +
               m_op1->to_string() + "," + m_op2->to_string() + "," + m_op3->to_string();
    }

    i32 ADD::compute_cst5_by_cst3(const std::bitset<3> &cst3, bool sn) {
        if (!sn) {
            u32 data = cst3.to_ulong();
            if (data == 0) {
                data = 8;
            }
            return static_cast<i32>(data);
        } else {
            i32 data = static_cast<i32>(cst3.to_ulong());
            data -= 8;
            return data;
        }
    }
}